在 2021 年 11 月初,React Router 正式釋出 v6 版本,身為 React 開發者已經按捺不住好奇心,想看看這個版本究竟增加了什麼功能?修改了什麼語法?因此就有了這篇文章的誕生。而在這篇文章中,我會介紹 v6 的一些新功能和 v5 有哪些地方不同。
v5:
<Switch>
  <Route path="/about">
    <About />
  </Route>
  <Route path="/topics">
    <Topics />
  </Route>
  <Route path="/">
    <Home />
  </Route>
</Switch>
v6:
<Routes>
  <Route path="/about" element={<About />} />
  <Route path="/topics" element={<Topics />} />
  <Route path="/" element={<Home />} />
</Routes>
在過去,需透過 exact 設定完全符合 url 時才會顯現指定的元件內容,現在 Router 會抓取最相近的 url 去呈現對應的元件。
<Routes>
  <Route path="/about" element={<About />} />
  <Route path="/topics" element={<Topics />} />
  <Route path="/topics/:topic" element={<SpecTopics />} />
  <Route path="/" element={<Home />} />
</Routes>
在 React Router v5 的官網有提到 activeClassName 被移除,v6 直接用 className 去判定即可。
<NavLink className={(navData) => navData.isActive ? "active" : "" } to="/about" />
之前的版本需要搭配 useRouteMatch 去組出巢狀路由,現在可以直接寫上想要的子路由上去。
v5:
export default function Topics() {
  let match = useRouteMatch();
  console.log(match);
  return (
    <div>
      <ul>
        <li>
          <Link to={`${match.url}/components`}>Components</Link>
        </li>
        <li>
          <Link to={`${match.url}/props-v-state`}>Props v. State</Link>
        </li>
      </ul>
      <Switch>
        <Route path={`${match.path}/:topicId`}>
          <Topic />
        </Route>
        <Route path={match.path}>
          <h3>Please select a topic.</h3>
        </Route>
      </Switch>
    </div>
  );
}
v6:
export default function Topics() {
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        <li>
          <Link to="components">Components</Link>
        </li>
        <li>
          <Link to="props-v-state">Props v. State</Link>
        </li>
      </ul>
      <Routes>
        <Route path=":topicId" element={<Topic />} />
        <Route path="*" element={<h3>Please select a topic.</h3>} />
      </Routes>
    </div>
  );
}
另外在 path 有沒有加上 '/' 結果都是一樣的:
// 路由為 `/hobby/favorite`
<Route
  path="favorite"
  element={<FavoriteHobbyListBody />}
/>
// 路由也為 `/hobby/favorite`
<Route
  path="/favorite"
  element={<FavoriteHobbyListBody />}
/>
在 Dashboard 元件內部,會根據路由 /messages 或 /tasks 在 <Outlet /> 的地方呈現對應的元件 <DashboardMessages /> or <DashboardTasks>。
function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* This element will render either <DashboardMessages> when the URL is
          "/messages", <DashboardTasks> at "/tasks", or null if it is "/"
      */}
      <Outlet />
    </div>
  );
}
function App() {
  return (
    <Routes>
      <Route path="/" element={<Dashboard />}>
        <Route
          path="messages"
          element={<DashboardMessages />}
        />
        <Route path="tasks" element={<DashboardTasks />} />
      </Route>
    </Routes>
  );
}
useNavigate 的第一個參數可以是路由或是數字,代表前進或回去的頁數。
v5:
import { useHistory } from "react-router-dom";
const News = () => {
  let history = useHistory();
  return (
    <>
      <button onClick={()=> history.push("/home")}>Home</button>
    </>
  );
}
v6:
import { useNavigate } from "react-router-dom";
const News = () => {
  let navigate = useNavigate();
  return (
    <>
      <button onClick={()=> navigate('/home')}>Home</button>
    </>
  );
}
v5:
import { useHistory } from "react-router-dom";
function Exchanges() {
  const { go, goBack, goForward } = useHistory();
  return (
    <>
      <button onClick={() => go(-2)}>
        2 steps back
      </button>
      <button onClick={goBack}>1 step back</button>
      <button onClick={goForward}>1 step forward</button>
      <button onClick={() => go(2)}>
        2 steps forward
      </button>
    </>
  );
}
v6:
import { useNavigate } from "react-router-dom";
function Exchanges() {
  const navigate = useNavigate();
  return (
    <>
      <button onClick={() => navigate(-2)}>
        2 steps back
      </button>
      <button onClick={() => navigate(-1)}>1 step back</button>
      <button onClick={() => navigate(1)}>
        1 step forward
      </button>
      <button onClick={() => navigate(2)}>
        2 steps forward
      </button>
    </>
  );
}
import React from "react";
import { useRoutes } from "react-router-dom";
const App = () => {
  let element = useRoutes([
    {
      path: "/",
      element: <Dashboard />,
      children: [
        {
          path: "messages",
          element: <DashboardMessages />
        },
        { path: "tasks", element: <DashboardTasks /> }
      ]
    },
    { path: "team", element: <AboutPage /> }
  ]);
  return element;
}
除了上述幾點之外,當然還有其他的變更,像是 React Router v6 整個 bundle size 縮小,更加輕量,不過以上就舉比較常用的幾點來說明,想了解更深入可以點擊我推薦的 youtube 影片或是到官網去閱讀文件囉!
最後,放上在鐵人賽 Day19 文章中介紹 React Router v5 的範例程式碼和修改後的 v6 版本程式碼範例提供給讀者做比較,不過範例中並沒有將文中的全部功能都使用上去,讀者可以自行練習看看哩!
v5 版本 codesandbox 範例
v6 版本 codesandbox 範例